/*
 * CRenderer.cpp
 *
 *  Created on: 04.09.2011
 *      Author: kirill
 *      Copyright: LGPL
 */

#include "CRenderer.h"
#include "CBulletDebugDrawer.h"
#include <SDL/SDL.h>
#include "../Constants.h"
#include <boost/bind.hpp>

#include "../Manager Resource/ManagerResource.h"

static const USHORT PIXEL_COLOR_DEPTH = 32;

CRenderer::CRenderer() :
			frameBuffer(NULL),
			renderBuffer(NULL),
			textureBuffer(NULL),
			initialized(false)
{

}

CRenderer::~CRenderer()
{
	if ( textureBuffer != NULL )
	{
		delete textureBuffer;
	}

	if ( frameBuffer != NULL )
	{
		delete frameBuffer;
	}

	if ( renderBuffer != NULL )
	{
		delete renderBuffer;
	}
}

bool
CRenderer::Init( const SCREEN_DIMENSION &Dimension )
{
    if ( SDL_Init ( SDL_INIT_VIDEO ) < 0 )
    {
        fprintf ( stderr, "Error while initializing SDL: %s\n", SDL_GetError() );
        return false;;
    }

    USHORT ComponentDepth = PIXEL_COLOR_DEPTH / 4;

    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1 );
    SDL_GL_SetAttribute(SDL_GL_RED_SIZE,   ComponentDepth);
    SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, ComponentDepth);
    SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE,  ComponentDepth);
    SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, ComponentDepth);

    SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE,  PIXEL_COLOR_DEPTH / 2 );
    SDL_GL_SetAttribute(SDL_GL_BUFFER_SIZE, 0 );

    SDL_GL_SetAttribute(SDL_GL_ACCUM_RED_SIZE,  0);
    SDL_GL_SetAttribute(SDL_GL_ACCUM_GREEN_SIZE,    0);
    SDL_GL_SetAttribute(SDL_GL_ACCUM_BLUE_SIZE, 0);
    SDL_GL_SetAttribute(SDL_GL_ACCUM_ALPHA_SIZE,    0);

    SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, (Dimension.multisampleBufferCount > 0) );
    SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES,  Dimension.multisampleBufferCount );

    if ( SDL_SetVideoMode ( Dimension.width, Dimension.height, PIXEL_COLOR_DEPTH, Dimension.sdlFlags  ) == NULL ) // | SDL_FULLSCREEN | SDL_HWSURFACE | SDL_OPENGL
    {
        printf ( "Error while setting up video mode: %s\n", SDL_GetError() );
        return false;;
    }

    SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); //Automatic repesd pushed key

    glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );
    glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
    glPixelStorei   ( GL_UNPACK_ALIGNMENT, 4 );

    glEnable( GL_DEPTH_TEST );
	glClearDepth( 1.0f );
    //glEnable( GL_CULL_FACE );
    //glCullFace( GL_FRONT );

    glEnable ( GL_DEPTH_TEST );

#ifdef WIN32
	GLenum err = glewInit();
    if (GLEW_OK != err)
	{
	   /* Problem: glewInit failed, something is seriously wrong. */
    	fprintf ( stderr, "Error in glewInit\n" );
	}
#endif

    dimension = Dimension;
    InitializeFrameBuffer( Dimension.width, Dimension.height );
    initialized = true;

    return true;
}

void
CRenderer::SetRenderParams( const VISUAL_PARAMS &Parametrs )
{

}

bool
CRenderer::StartNewFrame( const GLuint VertexArrayName )
{
	bool Result = false;

	if ( initialized )
	{
		ClearRenderBuffer();
		glBindVertexArray( VertexArrayName );
		Result = true;
	}
	return Result;
}

void
CRenderer::RenderCameraFrame( const std::vector<GAME_OBJECT*> &SortedVisibleObjects, const GAME_CAMERA &CurrentCamera )
{
	frameBuffer->BindFrameBuffer( true );

	ClearRenderBuffer();

	for ( std::vector<GAME_OBJECT*>::const_iterator it = SortedVisibleObjects.begin(), end = SortedVisibleObjects.end(); it != end; ++it )
	{
		GAME_OBJECT *TmpGameObject = *it.base();

		RenderObject( TmpGameObject, CurrentCamera.projectionMatrix, CurrentCamera.viewMatrix, CurrentCamera.lightDirection,
                      CurrentCamera.amblientColor );
	}

#ifdef DEBUG_PHYSICS
	CBulletDebugDrawer* DebugDrawer = CBulletDebugDrawer::GetInstance();

    //glDisable ( GL_DEPTH_TEST );
    DebugDrawer->OpenGlDraw( CurrentCamera.projectionMatrix, CurrentCamera.viewMatrix );
    //glEnable ( GL_DEPTH_TEST );
#endif

	frameBuffer->BindFrameBuffer( false );
	textureBuffer->UpdateBuffer();

#ifdef DEBUG_RENDERER
	if ( !frameBuffer->CheckFrameBuffer() )
	{
		fprintf( stdout, "Broken frame buffer\n" );
		exit(0);
	}
#endif
}

void
CRenderer::RenderFinalFrame( const std::vector<GUI_ELEMENT*> &GuiElementsArray, GAME_OBJECT* GuiShape )
{
	//Render texture buffer content
	GLuint TextureBuffer = textureBuffer->GetBufferName();

	//Disable depth test for correct overlapping
	glDisable( GL_DEPTH_TEST );
	RenderGuiObject( TextureBuffer, glm::vec2( 0.0f, 0.0f ), glm::vec2( 1.0f, 1.0f ), GuiShape );

	for ( std::vector<GUI_ELEMENT*>::const_iterator it = GuiElementsArray.begin(), end = GuiElementsArray.end();
		  it != end; ++it )
	{
		GUI_ELEMENT *TmpElement = *it.base();
		RenderGuiObject( TmpElement->texture, TmpElement->position, TmpElement->size, GuiShape );
	}

	glEnable( GL_DEPTH_TEST );

	glFlush(); // No need http://www.opengl.org/sdk/docs/man/
	SDL_GL_SwapBuffers();
}

SCREEN_DIMENSION
CRenderer::GetDimension() const
{
	return dimension;
}

void
CRenderer::RenderObject( GAME_OBJECT* Object, const glm::mat4 ProjectionMatrix, const glm::mat4 ViewMatrix,
		                 const glm::vec3 LightDirection, const glm::vec3 AmblientColor )
{
	SHADER_COMMON_PARAMS ShaderParams;
	ShaderParams.AmblientColor = AmblientColor;
	ShaderParams.LightDirection = LightDirection;
	ShaderParams.ProjectionMatrix = ProjectionMatrix;
	ShaderParams.ViewMatrix = ViewMatrix;

	Object->Render( ShaderParams, boost::bind( &CRenderer::RenderOperator, this, _1, _2, _3 ) );
}

void
CRenderer::RenderGuiObject( const GLuint Texture, const glm::vec2 &Position, const glm::vec2 &Size, GAME_OBJECT* GuiShape )
{
	glm::mat4 GuiMatrix = CalculateGuiMatrix( Position, Size );

	// ShaderParams.ModelMatrix will be calculated later as worldMatrix * SubobjectPosition(1)
	GuiShape->worldMatrix = GuiMatrix;

	SHADER_COMMON_PARAMS ShaderParams;
	ShaderParams.ProjectionMatrix = glm::mat4(1.0);
	ShaderParams.ViewMatrix = glm::mat4(1.0);

	TEXTURE_DESCRIPTOR TmpDescriptor;
	TmpDescriptor.textureGLid = Texture;
	TmpDescriptor.textureType = GL_TEXTURE_2D;
	ShaderParams.DiffuseTexture = TmpDescriptor;

	GuiShape->Render( ShaderParams, boost::bind( &CRenderer::RenderOperator, this, _1, _2, _3 ) );
}

void
CRenderer::InitializeFrameBuffer( const UINT WindowWidth, const UINT WindowHeight )
{
	frameBuffer = new CFrameBuffer();

    frameBuffer->BindFrameBuffer(true);

    textureBuffer = new CTextureRenderBuffer();
    textureBuffer->TuneRenderBuffer( GL_RGBA, WindowWidth, WindowHeight );

    renderBuffer = new CColorRenderBuffer(); //TODO перенести это до BindFrameBuffer()
    renderBuffer->TuneRenderBuffer( GL_DEPTH_COMPONENT24, WindowWidth, WindowHeight );

    frameBuffer->SetRenderTarget( GL_COLOR_ATTACHMENT0, textureBuffer );
    frameBuffer->SetRenderTarget( GL_DEPTH_ATTACHMENT, renderBuffer );

    frameBuffer->BindFrameBuffer(false);
}

glm::mat4
CRenderer::CalculateGuiMatrix( const glm::vec2 &Position, const glm::vec2 &Sizes ) const
{
	glm::vec2 Translation;
	Translation.x = Position.x * 2.0f;
	Translation.y = Position.y * 2.0f;

	glm::vec2 Factors;
	Factors.x = Sizes.x * 2.0f;
	Factors.y = Sizes.y * 2.0f;

	glm::mat4 ScaleMatrix = glm::scale( glm::mat4(1.0f), glm::vec3( Factors, 0.0 ) );
	glm::mat4 TransitionMatrix = glm::translate( glm::mat4(1.0f), glm::vec3( Translation, 1.0 ) );
	glm::mat4 ResultMatrix = TransitionMatrix * ScaleMatrix;

	return ResultMatrix;
}

void
CRenderer::ClearRenderBuffer()
{
	glViewport( 0, 0, dimension.width, dimension.height );
	glClearColor( 0.4f, 0.6f, 1.0f, 1.0f );
	glClearDepth( 1.0f );
	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
}

void
CRenderer::RenderOperator( RENDER_MODEL_INFO* RenderInfo, IShaderProgram *CurrentShader,
						   SHADER_COMMON_PARAMS& ShaderParams )
{
	CurrentShader->ActivateShader( true ); // Start program

	//Set standard parameters
	CurrentShader->SetParam( ShaderParams );

	glDrawRangeElementsBaseVertex( RenderInfo->primitiveType, 0, RenderInfo->vertexCount, RenderInfo->indexes.size(),
		             	 	   	   RenderInfo->indexType, RenderInfo->indexes.data(), RenderInfo->offsetInVertexBuffer );
	CurrentShader->ActivateShader( false );
}
